Overview

In this example we are going to create a simple C++ command line application. We will introduce both a memory and handle leak into the application and show how to leverage WonderLeak in order to identify the leaks and deal with false positives.

The source code for our example application is as follows:

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <memory>
#include <stdio.h>

bool runAndWait(const wchar_t* _program, const wchar_t* _parameters)
{
    if (_program == nullptr || _parameters == nullptr) {
        return false;
    }

    auto cmd_sz = wcslen(_program) + wcslen(_parameters) + 2;

    // Allocate a character array for the command line string.
    auto cmd = new wchar_t[cmd_sz];

    // Construct the command line string.
    swprintf_s(cmd, cmd_sz, L"%s %s", _program, _parameters );

    // Spawn a process...
    STARTUPINFOW si = { 0 };

    si.cb = sizeof(si);

    PROCESS_INFORMATION pi = { 0 };

    if (::CreateProcessW(
        nullptr,
        static_cast<LPWSTR>(cmd),
        nullptr,
        nullptr,
        FALSE,
        CREATE_UNICODE_ENVIRONMENT,
        nullptr,
        nullptr,
        &si,
        &pi
    ) == FALSE) {
        return false;
    }

    // Wait for the process to terminate.
    ::WaitForSingleObject(pi.hProcess, INFINITE);

    // Close the process handle.
    CloseHandle(pi.hProcess);

    // Destroy the command line string.
    delete[] cmd;

    return true;
}

int main(int argc, char* argv[])
{
    runAndWait(L"calc.exe", L"");

    runAndWait(L"this is not a valid program . exe", L"");

    return 0;
}
Profiling

Using our chosen IDE we will adjust the configuration settings (Learn how to configure Microsoft Visual Studio or Embarcadero C++ Builder) to allow WonderLeak successfully profile the application. After the application is built, we can run the applications executable and create a New Session via WonderLeak.

Clicking Profile within WonderLeak will run the target application and generate the session after the application has terminated.

We can see from the default snapshot in the resulting Session that several heap and handle allocations are live at the time the application terminated. We can group the allocations by 'External Code Hint' in order to better see where the allocation were originating from. The 'External Code Hint' is the symbol name of the last external symbol (ie. non user code) in the call stack for this allocation before the first non external symbol (ie. user code). From the snapshot we can see one allocation from the borland heap is still live and originated from a call to operator new[](unsigned long long) while several heap and handle allocations originating from CreateProcessWStub are still live.

If we select the single borland heap allocation in the snapshot, we can see from the call stack this allocation occurred when our example function allocates a character array for the command line string via auto cmd = new wchar_t[cmd_sz] as shown in the displayed source file when we navigate the call stack.

Reviewing our example code we can note that if runAndWait returns false when CreateProcess fails, the cmd allocation will not be deleted, thus leaking the allocation.

To understand the allocations occurring from CreateProcessW we should first consult the Microsoft Win32 API documentation. After reading this page we can note that all handles from the PROCESS_INFORMATION structure must be closed via CloseHandle. This includes both the hProcess handle which our example does close and the hThread handle which our example does not close. This is the cause of the thread handle leak as seen in the snapshot.

We modify our source to close the thread handle and we leverage a std::unique_ptr to scope the cmd allocation so that the allocation automatically frees when the function returns.

Running the application via WonderLeak a second time reveals we have resolved two of the leaks via the changes we made. The remaining leaks are occurring during CreateProcessWStub.

As we have reviewed the documentation for CreateProcessW we can conclude these allocations are internal to the underlying operating system and that our application code is not responsible for managing these allocations. We can now ignore these allocation by right clicking on the call stack entry for CreateProcessW in runAndWait and select Mark Source Line as Ignore.

A // WonderLeak-Ignore line comment will be added to the source file at the call site location, instructing WonderLeak to tag as ignore any allocation occurring at this call site. A default filter rule will exclude all allocations tagged as ignore. To see these allocations simply disable the filter rule for the snapshot.

Integration

We can leverage the API and Command Line Interface to integrate our example into an external pipeline in order to verify no unexpected leaks occur. First we will update our example source code to use the WonderLeakAPI_SnapshotW API function to take two snapshots, one before our example code runs, and one after our example code runs. We give these snapshots a name so we may query the snapshots by name at a later stage. We elect to use WONDERLEAK_TID_CURRENT to only capture allocations occurring in the current thread as opposed to all threads, as we dont want any unexpected allocations occurring within another thread to be in the snapshot.

We can now use the CLI to profile the example application and generate a new session file.

>C:\Program Files\WonderLeak\WonderLeakCLI.exe -P -S out1.wdls -- C:\Users\dev\Desktop\TestBCBWonderLeak\Win64\Debug\Project1.exe

Once the session file out1.wdls has been generated, we can query it through the CLI. We will perform a comparison between snapshot "Snap 1" and "Snap 2" and list the allocations in the comparison which were added (ie. an allocation that was present in "Snap 2" but not present in "Snap 1").

>C:\Program Files\WonderLeak\WonderLeakCLI.exe -Q -S out1.wdls --verbose --list-allocations "Snap 1" "Snap 2" --filter-default --text-include added

>echo %errorlevel%
0

If we examine the return value from the CLI (via %errorlevel%) we can see the value 0 is given which is the number of allocation listed by the CLI (ie. our example function runAndWait has not leaked any unexpected allocations).

Copyright © 2021, Relyze Software Limited